﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Linq;
using ScrumTable.BL.DM.DataManagement;
using ScrumTable.Common.Collections;
using ScrumTable.Common.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ScrumTable.DL.Data.Generic;
using ScrumTable.DL.Data.Generic.Cache;
using ScrumTable.DL.Data.Local.Test;
using ScrumTable.DL.Data.Schema;

using WorkItemBucket = ScrumTable.DL.Data.Generic.Cache.CacheElementListEntryBucket<ScrumTable.DL.Data.Generic.Cache.CacheWorkItem>;
using WorkItemSavePair = ScrumTable.DL.Data.Generic.Cache.CacheSavePair<ScrumTable.DL.Data.Generic.Cache.CacheElementListEntryBucket<ScrumTable.DL.Data.Generic.Cache.CacheWorkItem>>;

#endregion

namespace ScrumTable.BL.DM.Test.DataManagement
{


    /// <summary>
    /// This is the test class for Task class.
    /// </summary>
    [TestClass]
    public abstract class ScrumItemTest<T, TSchema> : DomainBaseTest<T, TSchema>
        where T : ScrumItemBase, new()
        where TSchema : SchemaBase
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        [TestMethod]
        public void TestBackEndId()
        {
            Assert.IsFalse(string.IsNullOrEmpty(ToTest.BackEndId));
        }

        #endregion

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new ScrumItemTest instance.
        /// </summary>
        public ScrumItemTest()
        {
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        [TestMethod]
        public void TestValueChangeBaseOnDMWithIndexer()
        {
            TestValueChangeWithEventOnWithIndexer("AssignedMember", DataObjectSchema["MemberId"], Resources.FullMember2IdChain);
            TestValueChangeWithEventOnWithIndexer("AssignedMember", DataObjectSchema["MemberId"], null, ValueChain<string>.Empty);
            TestValueChangeWithEventOnWithIndexer("Description", DataObjectSchema["Description"], "[New Description]");
            TestValueChangeWithEventOnWithIndexer("Description", DataObjectSchema["Description"], null);
            TestValueChangeWithEventOnWithIndexer("Name", DataObjectSchema["Name"], "[New Name]");
            TestValueChangeWithEventOnWithIndexer("Name", DataObjectSchema["Name"], string.Empty);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void TestValueChangeBaseOnDMWithIndexerTypeException()
        {
            TestValueChangeWithEventOnWithIndexer("AssignedMember", DataObjectSchema["MemberId"], 51 /* invalid type */);
        }

        [TestMethod]
        public void TestValueChangeBaseOnDM()
        {
            TestValueChangeWithEventOnDM("AssignedMember", Context.Projects[Resources.Project1Id].Members[Resources.Member2Id]);
            TestValueChangeWithEventOnDM("AssignedMember", null);
            TestValueChangeWithEventOnDM("Description", "[New Description]");
            TestValueChangeWithEventOnDM("Description", null, string.Empty);
            TestValueChangeWithEventOnDM("Name", "[New Name]");
            TestValueChangeWithEventOnDM("Name", string.Empty);
        }
        
        [TestMethod]
        public void TestValueChangeNameEmptyOnDM()
        {
            Assert.IsTrue(AssertUtil.ThrowsException<ArgumentNullException>(() =>TestValueChangeWithEventOnDM("Name", null)));
        }

        [TestMethod]
        public void TestValueChangeStateNullOnDM()
        {
            Assert.IsTrue(AssertUtil.ThrowsException<ArgumentNullException>(() => TestValueChangeWithEventOnDM("State", null)));
        }

        [TestMethod]
        public void TestIsSynchronized()
        {
            string oldName = ToTest.Name;
            Assert.IsTrue(ToTest.IsSynchronized);
            ToTest.Name = "[A New Name]";
            Assert.IsFalse(ToTest.IsSynchronized);
            ToTest.Name = oldName;
            Assert.IsTrue(ToTest.IsSynchronized);
        }

        [TestMethod]
        public void TesIsSynchronizedEvent()
        {
            const string newName = "[A New Name]";
            string oldName = ToTest.Name;

            NotifyPropertyTestStub<T> eventTester = new NotifyPropertyTestStub<T>(ToTest);
            eventTester.PropertyName = "IsSynchronized";

            using (eventTester.Register())
            {
                ToTest.Name = newName;
                Assert.AreEqual(1 /* expect first event*/, eventTester.PropertyFiredCount);
                Assert.AreEqual(1 /* expect first event*/, eventTester.PropertyFiringCount);

                ToTest.Name = newName;
                Assert.AreEqual(1 /* expect no event = 1*/, eventTester.PropertyFiredCount);
                Assert.AreEqual(1 /* expect no event = 1*/, eventTester.PropertyFiringCount);

                ToTest.Name = oldName;
                Assert.AreEqual(2 /* expect second (+1) event = 2*/, eventTester.PropertyFiredCount);
                Assert.AreEqual(2 /* expect second (+1) event = 2*/, eventTester.PropertyFiringCount);
            }
        }

        [TestMethod]
        public void TestSynchronizationErrorEvent()
        {
            const string newName = "[A New Name]";
            const string exceptionName = "Test Exception";
            string oldName = ToTest.Name;

            NotifyPropertyTestStub<T> eventTesterHasError = new NotifyPropertyTestStub<T>(ToTest);
            eventTesterHasError.PropertyName = "HasSynchronizationErrors";
            NotifyPropertyTestStub<T> eventTesterSyncErrors = new NotifyPropertyTestStub<T>(ToTest);
            eventTesterSyncErrors.PropertyName = "SynchronizationErrors";

            using (eventTesterHasError.Register())
            {
                using (eventTesterSyncErrors.Register())
                {
                    ToTest.Name = newName;
                    Assert.AreEqual(0 /* expect no event*/, eventTesterHasError.PropertyFiredCount);
                    Assert.AreEqual(0 /* expect no event*/, eventTesterHasError.PropertyFiringCount);
                    Assert.AreEqual(0 /* expect no event*/, eventTesterSyncErrors.PropertyFiredCount);
                    Assert.AreEqual(0 /* expect no event*/, eventTesterSyncErrors.PropertyFiringCount);

                    RejectChangesOnDataLayer();
                    Assert.AreEqual(1 /* expect first event*/, eventTesterHasError.PropertyFiredCount);
                    Assert.AreEqual(1 /* expect first event*/, eventTesterHasError.PropertyFiringCount);
                    Assert.AreEqual(1 /* expect first event*/, eventTesterSyncErrors.PropertyFiredCount);
                    Assert.AreEqual(1 /* expect first event*/, eventTesterSyncErrors.PropertyFiringCount);
                    Assert.IsTrue(ToTest.HasSynchronizationErrors);
                    Assert.IsNotNull(ToTest.SynchronizationErrors);
                    Assert.AreEqual(ToTest.SynchronizationErrors.Count, 0);

                    RejectChangesOnDataLayer(new Exception(exceptionName));
                    Assert.AreEqual(1 /* expect no event*/, eventTesterHasError.PropertyFiredCount);
                    Assert.AreEqual(1 /* expect no event*/, eventTesterHasError.PropertyFiringCount);
                    Assert.AreEqual(2 /* expect second (+1) event*/, eventTesterSyncErrors.PropertyFiredCount);
                    Assert.AreEqual(2 /* expect second (+1) event*/, eventTesterSyncErrors.PropertyFiringCount);
                    Assert.IsNotNull(ToTest.SynchronizationErrors);
                    Assert.AreEqual(ToTest.SynchronizationErrors.Count, 1);
                    Assert.AreEqual(ToTest.SynchronizationErrors[0].Error.Message, exceptionName);
                    
                    ToTest.Name = oldName;
                    Assert.AreEqual(2 /* expect second (+1) event*/, eventTesterHasError.PropertyFiredCount);
                    Assert.AreEqual(2 /* expect second (+1) event*/, eventTesterHasError.PropertyFiringCount);
                    Assert.AreEqual(3 /* expect third (+1) event*/, eventTesterSyncErrors.PropertyFiredCount);
                    Assert.AreEqual(3 /* expect third (+1) event*/, eventTesterSyncErrors.PropertyFiringCount);
                    Assert.IsFalse(ToTest.HasSynchronizationErrors);
                    Assert.IsNull(ToTest.SynchronizationErrors);
                }
            }
        }

        [TestMethod]
        public void TestSynchronizationErrorWithAccept()
        {
            const string newName = "[A New Name]";

            ToTest.Name = newName;
            Assert.IsFalse(ToTest.HasSynchronizationErrors);
            Assert.IsNull(ToTest.SynchronizationErrors);

            AcceptChangesOnDataLayer();
            Assert.IsFalse(ToTest.HasSynchronizationErrors);
            Assert.IsNull(ToTest.SynchronizationErrors);
            Assert.IsTrue(ToTest.IsSynchronized);
        }

        [TestMethod]
        public void TestSynchronizationErrorsEvent()
        {
            const string newName = "[A New Name]";
            string oldName = ToTest.Name;
            
            NotifyPropertyTestStub<T> eventTester = new NotifyPropertyTestStub<T>(ToTest);
            eventTester.PropertyName = "SynchronizationErrors";

            using (eventTester.Register())
            {
                ToTest.Name = newName;
                Assert.AreEqual(0 /* expect no event*/, eventTester.PropertyFiredCount);
                Assert.AreEqual(0 /* expect no event*/, eventTester.PropertyFiringCount);

                RejectChangesOnDataLayer();

                ToTest.Name = newName;
                Assert.AreEqual(1 /* expect first event*/, eventTester.PropertyFiredCount);
                Assert.AreEqual(1 /* expect first event*/, eventTester.PropertyFiringCount);
                Assert.IsTrue(ToTest.HasSynchronizationErrors);

                ToTest.Name = oldName;
                Assert.AreEqual(2 /* expect second (+1) event*/, eventTester.PropertyFiredCount);
                Assert.AreEqual(2 /* expect second (+1) event*/, eventTester.PropertyFiringCount);
                Assert.IsFalse(ToTest.HasSynchronizationErrors);
            }
        }

        [TestMethod]
        public void TestIsSynchronizedWithReject()
        {
            Assert.IsTrue(ToTest.IsSynchronized);
            ToTest.Name = "[A New Name]";
            Assert.IsFalse(ToTest.IsSynchronized);
            RejectChangesOnDataLayer();
            Assert.IsFalse(ToTest.IsSynchronized);
            Assert.IsTrue(ToTest.HasSynchronizationErrors);
        }

        [TestMethod]
        public void TestIsSynchronizedWithAccept()
        {
            Assert.IsTrue(ToTest.IsSynchronized);
            ToTest.Name = "[A New Name]";
            Assert.IsFalse(ToTest.IsSynchronized);
            AcceptChangesOnDataLayer();
            Assert.IsTrue(ToTest.IsSynchronized);
            Assert.IsFalse(ToTest.HasSynchronizationErrors);
        }

        /// <summary>
        /// Tests the value change (and the events) of the given indexed property.
        /// </summary>
        protected void TestValueChangeWithEventOnWithIndexer(string propertyName, string schemaName, object newValue)
        {
            TestValueChangeWithEventOnWithIndexer(propertyName, schemaName, newValue, newValue);
        }

        /// <summary>
        /// Tests the value change (and the events) of the given indexed property.
        /// </summary>
        protected void TestValueChangeWithEventOnWithIndexer(string propertyName, string schemaName, object newValue, object resultValue)
        {
            TestValueChangeWithEvent(
                propertyName,
                () =>
                {
                    ToTest[schemaName] = newValue;
                    return resultValue;
                },
                () =>
                {
                    return ToTest[schemaName];
                });
        }

        protected void TestStateSet(ScrumState oldState, ScrumState newState)
        {
            ToTest.State = oldState;
            Assert.AreEqual(oldState, ToTest.State);
            ToTest.State = newState;
            Assert.AreEqual(newState, ToTest.State);
        }



        #region Data Driver Actions

        protected void AcceptChangesOnDataLayer()
        {
            if (!(DataContext is GenericDataContextBase))
            {
                return;
            }

            GenericDataContextBase dataContext = (GenericDataContextBase)DataContext;
            CacheDataChanges changes = dataContext.ExportChanges();

            WorkItemBucket newBucket = new WorkItemBucket { Element = changes.ChangedWorkItems.First().Value.Element };
            WorkItemSavePair newSavePair = new WorkItemSavePair(newBucket) { State = CacheSavePairState.Saved };

            changes.WorkItemsToSync[newBucket.Element.UniqueId] = newSavePair;
            dataContext.ImportChanges(changes);
        }

        protected void RejectChangesOnDataLayer(Exception error = null)
        {
            if (!(DataContext is GenericDataContextBase))
            {
                return;
            }

            GenericDataContextBase dataContext = (GenericDataContextBase) DataContext;
            CacheDataChanges changes = dataContext.ExportChanges();

            if (error != null)
            {
                changes.WorkItemsToSync.First().Value.Errors.Add(new CacheSaveError(error));
            }
            dataContext.ImportChanges(changes);
        }

        #endregion


        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}
